package e.odbo;

import com.openbravo.data.basic.BasicException;
import com.openbravo.data.loader.I_Session;
import com.openbravo.data.loader.TableDBSentenceBuilder;
import com.openbravo.data.loader.dialect.AbstractDBDialect;
import com.openbravo.data.loader.sentence.I_DBExecInterceptor;
import e.odbo.data.dsl.query.QBF;
import e.odbo.data.dsl.query.QBFParameter;
import e.odbo.data.dsl.query.QBFParameters;
import com.openbravo.data.loader.sentence.PreparedSentence;
import com.openbravo.data.loader.sentence.StaticSentence;
import com.openbravo.data.loader.sentence.batch.BatchSentenceScript;
import com.openbravo.data.loader.serialize.SerializableRead;
import com.openbravo.data.loader.serialize.serializer.SerializerRead;
import com.openbravo.data.loader.serialize.serializer.SerializerWrite;
import com.openbravo.data.loader.serialize.serializer.write.SerializerWriteBasic;
import com.openbravo.data.loader.sqlbuilder.QBFBuilder;
import e.odbo.data.dao.table.I_TableSentenceBuilder;
import e.odbo.data.model.*;
import e.odbo.data.model.comparable.VersionComparable;
import e.odbo.data.model.smaple.PK;
import e.odbo.data.AbstractConfigEntry;
import e.odbo.data.OdbO;
import e.odbo.data.dao.BaseDAO;
import e.odbo.data.dsl.operation.QueryOperation;
import e.odbo.data.todo.plugin.I_DBPlugin;
import e.odbo.data.dsl.Select;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

public class DB {

    public static final Logger logger= LoggerFactory.getLogger(DB.class);

    //interceptor
    private static List<I_DBExecInterceptor> dbExecInterrupts=null;

    public static void addDBExecInterrupt(I_DBExecInterceptor dbExecInterrupt){
        if(dbExecInterrupts==null)
            dbExecInterrupts=new ArrayList<>();
        dbExecInterrupts.add(dbExecInterrupt);
    }

    public static void removeDBExecInterrupt(I_DBExecInterceptor dbExecInterrupt){
        if(dbExecInterrupts==null)
            return;
        dbExecInterrupts.remove(dbExecInterrupt);
    }

    public static List<I_DBExecInterceptor> getDBExecInterrupts(){
        return dbExecInterrupts;
    }

    public static boolean isDBExecInterrupts(){
        if(dbExecInterrupts==null||dbExecInterrupts.size()==0)
            return false;
        return true;
    }


    public static String getListSQL(BaseDAO base, int[] columnIndexs) throws BasicException {
        I_TableSentenceBuilder sqlBuilder=base.getTableBuilder();
        if(sqlBuilder instanceof TableDBSentenceBuilder)
            return ((TableDBSentenceBuilder)sqlBuilder).getBaseListSQL(base.getTable(),columnIndexs);
        throw new BasicException("just db support!");
    }

    public static String getListSQL(BaseDAO base) throws BasicException {
        I_TableSentenceBuilder sqlBuilder=base.getTableBuilder();
        if(sqlBuilder instanceof TableDBSentenceBuilder)
            return ((TableDBSentenceBuilder)sqlBuilder).getBaseListSQL(base.getTable());
        throw new BasicException("just db support!");
    }

    public static String getListSQL(BaseDAO base,String aliseName) throws BasicException {
        I_TableSentenceBuilder sqlBuilder=base.getTableBuilder();
        if(sqlBuilder instanceof TableDBSentenceBuilder)
            return ((TableDBSentenceBuilder)sqlBuilder).getBaseListSQL(base.getTable(),aliseName);
        throw new BasicException("just db support!");
    }

    public static Map<String,Object> globalConfigs=new HashMap<>();

    public static Map<String,Object> configs=new HashMap<>();



    public static void globalConfig(AbstractConfigEntry entry){
         globalConfigs.put(entry.getEntryName(),entry.getValue());
    }

    public DB config(AbstractConfigEntry entry){
        configs.put(entry.getEntryName(),entry.getValue());
        return this;
    }

    public Object getConfig(String entryName){
        if(configs.containsKey(entryName))
            return configs.get(entryName);
        return globalConfigs.get(entryName);
    }

    I_Session dbSession;

    AbstractDBDialect dbDialect;

    public DB(I_Session dbSession) {
        this.dbSession=dbSession;
        dbDialect= (AbstractDBDialect) dbSession.getDialect();
    }

    public I_Session getDbSession() {
        return dbSession;
    }

    public AbstractDBDialect getDbDialect() {
        return dbDialect;
    }

    public static DB getInstance(I_Session dbSession){
        return new DB(dbSession);
    }

    Map<String, Table> tables=new HashMap<>();

    public DB model(Table ...tables){
        if(tables==null)
            return this;
        for(Table table:tables){
            this.tables.put(table.getName(),table);
        }
        return this;
    }

    public DB model(DataBase dataBase){
        return model(dataBase.getTables().toArray(new Table[dataBase.getTables().size()]));
    }

    public DB model(String name, Column...columns){
        this.tables.put(name,new Table(name,columns));
        return this;
    }

    public void use(I_DBPlugin plugin){
        plugin.init(this);
    }

    public String generateTablesDDL(){
        Collection<Table> list=tables.values();
        return dbDialect.getDDLFactory().createTablesDropFirst(list.toArray(new Table[list.size()]));
    }

    public String generateDataBaseDDL(DataBase dataBase){
        return dbDialect.getDDLFactory().getScript(dataBase);
    }

    public String generateDataBaseUpDDL(DataBase dataBase)throws BasicException{
        String oldVersion=getCurrentDataBaseVersion(dataBase.getName());
        Migration[] upMigration=dataBase.getMigrationsUP(oldVersion,dataBase.getVersion());
        if(null==upMigration)
            throw new BasicException("Migration no find for:"+oldVersion+" to :"+dataBase.getVersion());
        return dbDialect.getDDLFactory().getMigrationUpScript(upMigration);
    }

    public void setup(DataBase dataBase) throws BasicException{
        DatabaseState databaseState=DatabaseState.NOSETUP;
        try{
            databaseState=check(dataBase);
        }catch (BasicException e){
            logger.debug(e.getMessage());
        }
        switch (databaseState){
            case NOSETUP:
                installDataBase(dataBase);
                break;
            case UPDATEABLE:
                updateDataBase(dataBase);
                break;
            case INSTALLED:
        }
    }

    private void installDataBase(DataBase dataBase)throws BasicException{
        String sqlScript=generateDataBaseDDL(dataBase);
        BatchSentenceScript batchSentence=new BatchSentenceScript(dbSession,sqlScript);
        batchSentence.exec();
    }

    private void updateDataBase(DataBase dataBase)throws BasicException{
        String sqlScript=generateDataBaseUpDDL(dataBase);
        BatchSentenceScript batchSentence=new BatchSentenceScript(dbSession,sqlScript);
        batchSentence.exec();
    }


    public void installSample(TableData... sampleDatas)throws BasicException{
        StringBuilder sqlScript=new StringBuilder();
        for(TableData sampleData:sampleDatas)
            sqlScript.append(dbDialect.getDDLFactory().insert(sampleData));
        BatchSentenceScript batchSentence=new BatchSentenceScript(dbSession,sqlScript.toString());
        batchSentence.exec();
    }

    public static enum DatabaseState{
        INSTALLED,UPDATEABLE,NOSETUP
    }

    public DatabaseState check(DataBase dataBase)throws BasicException{
        return check(dataBase.getName(),dataBase.getVersionComparable());
    }


    public DatabaseState check(String dataBaseName,VersionComparable versionComparable)throws BasicException{
        String oldVersion=getCurrentDataBaseVersion(dataBaseName);
        if(null==oldVersion)
            return DatabaseState.NOSETUP;
        if(versionComparable==null)
            throw new BasicException("not support version Comparable,are you set.");
        if(!versionComparable.match(oldVersion))
            throw new BasicException(" check version Comparable no eq.");
        int comperRet=versionComparable.compareTo(oldVersion);
        if(comperRet>0)
            return DatabaseState.UPDATEABLE;
        else if(comperRet<0)
            throw new BasicException(" current version:"+ versionComparable.getCurrentVersion()+" less then old version:"+oldVersion);
       return DatabaseState.INSTALLED;
    }

    public String getCurrentDataBaseVersion(String dataBaseName) throws BasicException {
        String sql="select version from "+DataBase.versionTable.getName()+" where "+PK.Column_Name+"='"+dataBaseName+"'";
        StaticSentence staticSentence=new StaticSentence(dbSession, sql,null, OdbO.READER.STRING);
        return  (String) staticSentence.find();
    }

    //exec
    public int exec(String sql)throws BasicException{
       return new StaticSentence(dbSession,sql).exec();
    }

    public List execPrepared(String sql, SerializableRead mapper, SerializerWrite paras) throws BasicException{
        throw new BasicException("no suport now");
    }


    //insert
    public int insert(String table,Object[] values) throws BasicException {
        Table table1=tables.get(table);
        PreparedSentence preparedSentence=new PreparedSentence(dbSession,Table.getInsertSQL(table1),new SerializerWriteBasic(table1.getColumnDatas()));
        return preparedSentence.exec(values);
    }

    public int insert(String table,Map values)throws BasicException{
        Table table1=tables.get(table);
        String[] columns=(String[])values.keySet().toArray();
        PreparedSentence preparedSentence=new PreparedSentence(dbSession,Table.getInsertSQL(table1,columns),new SerializerWriteBasic(table1.getColumnDatas(columns)));
        return preparedSentence.exec(values.values().toArray());
    }

    public int[] insert(String table,Object[] ...values) throws BasicException {
        Table table1=tables.get(table);
        PreparedSentence preparedSentence=new PreparedSentence(dbSession,Table.getInsertSQL(table1),new SerializerWriteBasic(table1.getColumnDatas()));
        int[] retValue=new int[values.length];
        for(int i=0;i<values.length;i++)
            retValue[i]=preparedSentence.exec(values[i]);
        return retValue;
    }


    //update
    public int update(String table, Object[] values, QBFParameters qbfParameters)throws BasicException{
       throw new BasicException("no suport now");
    }

    public int update(String table, Map values, QBFParameters qbfParameters)throws BasicException{
        throw new BasicException("no suport now");
    }

    //delete
    public int delete(String table,Object key)throws BasicException{
        throw new BasicException("no suport now");
    }

    public int delete(String table,QBFParameters qbfParameters)throws BasicException{
        throw new BasicException("no suport now");
    }

    //count
    public int count(String table)throws BasicException{
        throw new BasicException("no suport now");
    }

    public int count(String table,QBFParameters qbfParameters)throws BasicException{
        throw new BasicException("no suport now");
    }

    //find
    public <T> T find(String table,QBFParameter qbfParameter,SerializerRead<T> mapper)throws BasicException{
        return find(table,QBF.c().and(qbfParameter).build(),mapper);
    }

    public <T> T find(String table,QBFParameters qbfParameters,SerializerRead<T> mapper)throws BasicException{
        Table table1=tables.get(table);
        StaticSentence staticSentence=new StaticSentence(dbSession, Table.getListSQL(table1)+QBFBuilder.getFilterSQL(qbfParameters),null,mapper);
        return (T)staticSentence.find();
    }

    //query
    public List query(String table, QBFParameter qbfParameter)throws BasicException{
        throw new BasicException("no suport now");
    }

    public List query(String table,QBFParameters qbfParameters)throws BasicException{
        throw new BasicException("no suport now");
    }

    //dsl
    public List select(String table, String[] columns, QBFParameter qbfParameter)throws BasicException{
        throw new BasicException("no suport now");
    }

    public List select(String table,String[] columns,QBFParameters qbfParameters)throws BasicException{
        throw new BasicException("no suport now");
    }

    public List select(String table,String[] columns, QBFParameters qbfParameters, QueryOperation queryOperation)throws BasicException{
        throw new BasicException("no suport now");
    }

    //adv dsl
    public List select(Select dsl, SerializerRead mapper)throws BasicException{
        return new StaticSentence(dbSession, dbSession.getDialect().getSQL(dsl),null,mapper).list();
    }

}
